使用protobuf (proto3, C++和go语言)

您所在的位置:网站首页 c语言 uint128输出 使用protobuf (proto3, C++和go语言)

使用protobuf (proto3, C++和go语言)

2023-10-07 10:23| 来源: 网络整理| 查看: 265

在这里,我先讲述C++使用protobuf,之后,会补充使用go语言使用protobuf。

使用protobuf需要有如下步骤:

在.proto文件中定义消息(message)格式。 使用protobuf的编译器编译.proto文件成为相应的语言代码。 使用对应语言的protobuf API读写消息。 在这里,我直接使用了官方的示例,之后打算使用grpc简单转写这个示例。官方示例实现了一个称为addressbook的功能,具体包括两部分,第一部分是向addressbook中添加个人信息,第二部分是,读取个人信息。在这里实现的第一步是在.proto中定义个人的结构,当然,如果你想采取自顶向下设计的话,可能会先定义对用户接口。

下面我们看一下定义的.proto的文件的源代码:

// [START declaration] syntax = "proto3"; package tutorial; import "google/protobuf/timestamp.proto"; // [END declaration] // [START messages] message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5; } // Our address book file is just one of these. message AddressBook { repeated Person people = 1; } // [END messages]

这里,我们对.proto文件所使用的语法进行简单讲解。

1)protobuf使用的.proto文件以包声明开始,包声明和C++中的namespace对应,在某个包声明中定义的消息,会出现在对应的namespace命名空间中。import语句用来导入其他.proto文件中的消息定义,这样就可以在多个.proto文件中定义消息,然后关联使用了。

2)然后,你需要定义消息结构。一个消息包括多个带类型的成员。protobuf有许多标准的简单数据类型,包括bool, int32, float,double以及string, protobuf自带的.proto文件中也有一些消息结构定义,例如上面出现的google.protobuf.Timestamp。当然,你也可以根据这些类型,进一步构造其他消息,例如上面的Person包含了PhoneNumber消息,AddressBook包含了Person消息。你也可以在其他消息中定义消息类型,例如上面出现在PhoneNUmber在Person中进行定义。你还可以定义enum类型,例如上面的PhoneType,包含MOBILE,HOME和WORK三个可选值。

“=1”, “=2”是用来在二进制编码中标识对应字段的tag。tag在1-15范围内只需要一个byte来编码,而较大的数字需要两个byte来编码,所以对于常用的那些字段,可以使用1-15范围内的tag。

另外,每一个tag可以使用如下修饰符修饰:

(1)singular: 表示这个字段可以有一个,也可以没有。如果没有的话,在编码的时候,不会占用空间。

(2)repeated: 表示这个字段会重复0次或者更多次,这个字段里的值会按照顺序编码。

2. 定义完了.proto文件,下一步就是编译这个proto文件,我们假设这个proto文件名为addressbook.proto。为了编译这个文件,运行如下的语句:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/address.proto

其中-I指定proto文件所在的位置,$DST_DIR指定生成文件所在的位置,这里--cpp_out表示生成文件为C++文件,生成目录在$DST_DIR,$SRC_DIR/addressbook.proto。

如果你在proto所在文件调用上述命令,可以简写如下:

protoc --cpp_out=. addressbook.proto

调用上述命令,生成的文件为addressbook.pb.h和addressbook.pb.cc。可以推测,对于xxx.proto,生成文件应该为xxx.pb.h和xxx.pb.cc。

 

下面简单查看一些类的定义:

class Person_PhoneNumber : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition::tutorial.Person.PhoneNumber) */ { public:   Person_PhoneNumber();   virtual ~Person_PhoneNumber();   static const ::google::protobuf::Descriptor* descriptor() {     return default_instance().GetDescriptor();   }   // accessors ----------------------------------------------------------------   // string number = 1;   void clear_number();   const ::std::string& number() const;   void set_number(const ::std::string& value);   void set_number(::std::string&& value);   void set_number(const char* value);   void set_number(const char* value, size_t size);   ::std::string* mutable_number();   ::std::string* release_number();   void set_allocated_number(::std::string* number);   // .tutorial.Person.PhoneType type = 2;   void clear_type();   ::tutorial::Person_PhoneType type() const;   void set_type(::tutorial::Person_PhoneType value); };

这里的descriptor函数,可以用于反射处理。proto文件在编译时,会提供比较详细的操作和获取函数,当做普通类处理,也会很方便。另外注意这个函数的命令Person_PhoneNumber。在proto文件中,Person为外部类,PhoneNumber是内嵌在Person中的类,对应生成的类名就是按照上面的规则。注意下mutable_number方法,这个方法在没有设置number的时候也可以调用,在调用时,number会被初始化为空字符串。

enum Person_PhoneType {   Person_PhoneType_MOBILE = 0,   Person_PhoneType_HOME = 1,   Person_PhoneType_WORK = 2,   ... }; class Person : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition: tutorial.Person) */ { public:   Person();   virtual ~Person();   static const ::google::protobuf::Descriptor* descriptor() {     return default_instance().GetDescriptor();   }   typedef Person_PhoneNumber PhoneNumber;   typedef Person_PhoneType PhoneType;   static const PhoneType MOBILE = Person_PhoneType_MOBILE;   static const PhoneType HOME = Person_PhoneType_HOME;   static const PhoneType WORK = Person_PhoneType_WORK;   static inline bool PhoneType_IsValid(int value) {     return Person_PhoneType_IsValid(value);   }   static inline const ::std::string& PhoneType_Name(PhoneType value) {     return Person_PhoneType_Name(value);   }   static inline bool PhoneType_Parse(const ::std::string& name, PhoneType* value) {     return Person_PhoneType_Parse(name, value);   }   // accessors -------------------------------------------   // repeated .tutorial.Person.PhoneNumber phones = 4;   int phones_size() const;   void clear_phones();   ::tutorial::Person_PhoneNumber* mutable_phones(int index);   ::google::protobuf::RepeatedPtrField* mutable_phones();   const ::tutorial::Person_PhoneNumber& phones(int index) const;   ::tutorial::Person_PhoneNumber* add_phones();   const ::google::protobuf::RepeatedPtrField& phones() const;   // string name = 1;   // string email = 3;   // .google.protobuf.Timestamp last_updated = 5;   bool has_last_updated() const;   void clear_last_updated();   const ::google::protobuf::Timestamp& last_updated() const;   ::google::protobuf::Timestamp* release_last_updated();   ::google::protobuf::Timestamp* mutable_last_updated();   void set_allocated_last_updated(::google::protobuf::Timestamp* last_updated);   // int32 id = 2;   void clear_id();   ::google::protobuf::int32 id() const;   void set_id(::google::protobuf::int32 value); };

这个类的定义和上面的Person_PhoneNumber没有太大的差别,其中的typedef类型重定义和const定义,通过这种方式,来使得PhoneNumber一类的内嵌类使用起来更加自然,更符合.proto文件中的定义。可以查看一下不同类型的成员的不同操作方法。同一个类型的成员,提供的操作方法基本相同。另外注意一点,Person_PhoneNumber和Person类都继承于::google::protobuf::Message。

 

标准的Message方法

每一个消息类都有很多别的方法,让你来检查或者操作整个消息,消息类有这些方法,因为继承于Message类,或者直接使用下面的方法,或者重写了虚函数。

1) bool IsInitialialized() const; : 检查是不是所有必需的字段都已经设置, 这个函数是虚函数。

2) string DebugString() const; : 返回一个可读的消息表示,很适合用于调试。这个函数的实现如下:

string Message::DebugString() const { string debug_string; TextFormat::Printer printer; printer.SetExpandAny(true); printer.PrintToString(*this, &debug_string); return debug_string; }

输出的大致内容可以参考下面的函数:

void TextFormat::Printer::Print(const Message& message, TextGenerator* generator) const { const Descriptor* descriptor = message.GetDescriptor(); auto itr = custom_message_printers_.find(descriptor); if (itr != custom_message_printers_.end()) { itr->second->Print(message, single_line_mode_, generator); return; } const Reflection* reflection = message.GetReflection(); if (descriptor->full_name() == internal::kAnyFullTypeName && expand_any_ && PrintAny(message, generator)) { return; } std::vector fields; if (descriptor->options().map_entry()) { fields.push_back(descriptor->field(0)); fields.push_back(descriptor->field(1)); } else { reflection->ListFields(message, &fields); } if (print_message_fields_in_index_order_) { std::sort(fields.begin(), fields.end(), FieldIndexSorter()); } for (int i = 0; i < fields.size(); i++) { PrintField(message, reflection, fields[i], generator); } if (!hide_unknown_fields_) { PrintUnknownFields(reflection->GetUnknownFields(message), generator); } }

1) void CopyFrom(const Person& from); : 使用from的值来覆盖现有值,这个函数是虚函数。

2) void Clear(); 清理所有的元素,将消息重置为空值状态,这个函数是虚函数。

 

消息的解析和序列号

每一个消息类都有方法用protobuf二进制格式写入到string或者输出流,也可以从string或者输入流读取数据,来设置值。这些方法都是来自于Message类(或者间接来自于MessageLite)。这些方法包括:

1)bool SerializeToString(string* output) const; :将消息转化成protobuf二进制存储到string中,注意存储的是二进制,而不是文本。

2)bool ParseFromString(const string& data); : 从给定的string中解析消息。

3)bool SerializeToOstream(ostream* output) const; : 将消息写入到给定的C++ ostream中。

4)bool ParseFromIstream(istream* input); : 从C++ istream中解析消息。

还有一些用于解析和序列号的函数,可以自行查看。

 

3. 使用proto文件编译生成的源码和protobuf官方提供的API接口进行操作

我们先查看一下添加个人的应用:

#include #include #include #include #include #include "addressbook.pb.h" using namespace std; using google::protobuf::util::TimeUtil; // This function fills in a Person message based on user input. void PromptForAddress(tutorial::Person* person) { cout > id; person->set_id(id); cin.ignore(256, '\n'); cout mutable_name()); cout set_email(email); } while (true) { cout add_phones(); phone_number->set_number(number); cout set_type(tutorial::Person::MOBILE); } else if (type == "home") { phone_number->set_type(tutorial::Person::HOME); } else if (type == "work") { phone_number->set_type(tutorial::Person::WORK); } else { cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3